import { DataTypes, Model, Sequelize } from "sequelize";

// yjs_state 用二进制（Uint8Array）存是为了小体积、可跨语言稳定序列化、快速合并与回放，保留 CRDT 必需的时钟/结构信息，JSON 做不到。
// yjs_updates 是“每次编辑产生的增量更新（二进制）”，可用于实时广播、离线重放、版本回退与审计；通常配合定期快照一起用。
// 详细要点
// 为什么是二进制
// 压缩高：变长整型、结构化编码，体积远小于等价 JSON。
// 信息完整：CRDT 的时钟、因果关系、结构（Map/Array/XmlFragment）都被编码；JSON 难以无损承载。
// 跨语言/稳定：Yjs 定义了稳定的 binary 编码（Y.encodeStateAsUpdate / Y.applyUpdate），任何语言实现可互通。
// 性能佳：apply/merge 都走 C/JS 的字节级解析，远快于解析大 JSON。
// yjs_state 是什么
// 定义：某一时刻“完整文档状态”的二进制快照，通常来自 Y.encodeStateAsUpdate(doc)。
// 用途：新客户端快速恢复；服务端“强制同步/合并”；宕机或重启后立即恢复一致状态。
// 存储：MySQL LONGBLOB（如 yjs_documents.yjs_state），查看时常显示为 HEX（X'…'）或可转 Base64。
// yjs_updates 是什么
// 定义：doc.on('update') 产生的“增量更改”，比如“插入字符”“删除范围”“设置样式”都会被编码为一个 update（二进制 Uint8Array）。
// 用途：
// 实时：广播给同房间的其他客户端（socket.emit('update', …)）。
// 离线：客户端断网时排队，重连后逐条发送；服务端也可持久化为“可重放日志”。
// 版本/回放：用“最近快照 yjs_state + 之后的 yjs_updates”即可追到最新；也可按时间/事务做回退与审计。
// 存储形态：数据库中常以 JSON 数组保存（把每条 Uint8Array 转成 base64/hex 字符串），例如：
// [{"update":"ARgP8w...","userId":"u1","ts":1719990000000},{"update":"AQ0K9Q...","userId":"u2","ts":1719990001200}]
// 二者关系与实践
// 还原路径：空文档 + 全量 updates = 最新状态；或“最近快照” + “快照后 updates” = 最新状态。
// 生产推荐：定期生成快照（更新 yjs_state），只保留最近 N 条 updates（定时清理），在性能与回放能力之间平衡。
// 为什么不只用 JSON：JSON 不含 CRDT 因果与结构信息，无法正确合并并发编辑，体积大、回放慢，不适合作为协同事实源。

// 核心原理：Yjs 基于 CRDT（Conflict-free Replicated Data Type，无冲突复制数据类型）。每个编辑操作都是“可交换、可结合、幂等”的更新，任意顺序合并都得到同一最终结果（强最终一致）。
// 怎么做到“不冲突”
// 唯一标识和逻辑时钟：每个插入的字符/块都有全局唯一ID（clientId + clock），并带有因果关系。并发操作通过确定性规则（如按ID顺序）排好队。
// 并发插入的确定性合并：两人同时在同一位置插入内容，Yjs用稳定的“tie-breaker”（客户端ID、Lamport时钟等）决定顺序，所有副本都会得出相同顺序。
// 删除为逻辑删除：删除记录的是“删除某个ID元素”，不是按索引删，避免并发移动/插入导致的错位；后续可垃圾回收。
// 结构化CRDT：文档用 Y.Array / Y.Map / Y.XmlFragment 表示，局部编辑只影响对应子结构；合并时仅按子结构规则解决。
// 因果一致与重放：更新携带依赖关系，晚到的包按依赖补齐后再应用；update 是幂等的，重复应用也不会改变结果。
// 无需中心锁：每个副本都能独立接受本地编辑与远端更新，网络分区/离线也可编辑，重连后按CRDT规则合并，收敛到同一状态。
// 为什么适合协同
// 事件顺序无关：操作可乱序到达，最终一致。
// 低带宽高吞吐：传的是二进制增量（Uint8Array），只发变更。
// 离线可编辑：断网继续写，重连自动合并，无需“手动解决冲突”。
// 小例子（并发插入）
// 用户A与B同时在“ab|cd”的“|”处插入“X”和“Y”。两条插入各自带ID：A:(idA)，B:(idB)。
// 合并时按固定排序（如先小ID），所有副本统一变为“abXYcd”或“abYXcd”（取决于排序规则），结果一致，无需人工冲突处理。
// 小例子（并发删除与插入）
// B 删除了带ID=e5的字符，A 又在它前面插入“Z”。应用次序不同也没关系：删除只影响 e5；插入“Z”的ID独立存在，最终都会保留“Z”。
// 总结
// Yjs 不用“锁/事务串行化”来避免冲突，而是用 CRDT 的数学性质与确定性规则，让“冲突自动可合并”，保证最终一致且用户无感。
/**
 * Yjs文档持久化模型接口
 */
export interface YjsDocumentAttributes {
  id?: number;
  document_id: string;
  room_name: string;
  yjs_state: Buffer; // 文档的数据 存的是二进制 原始数据
  yjs_updates?: any[]; // JSON数组格式的更新记录 增量更新
  last_updated?: Date;
  version: number;
  created_at?: Date;
  updated_at?: Date;
}

/**
 * Yjs文档持久化模型类
 */
export class YjsDocument extends Model<YjsDocumentAttributes> implements YjsDocumentAttributes {
  public id!: number;
  public document_id!: string;
  public room_name!: string;
  public yjs_state!: Buffer;
  public yjs_updates!: any[];
  public last_updated!: Date;
  public version!: number;
  public created_at!: Date;
  public updated_at!: Date;

  // 关联属性
  public readonly document?: any;
}

/**
 * 初始化Yjs文档持久化模型
 */
export function initYjsDocumentModel(sequelize: Sequelize): typeof YjsDocument {
  YjsDocument.init(
    {
      id: {
        type: DataTypes.BIGINT,
        primaryKey: true,
        autoIncrement: true,
        comment: "Yjs文档ID，主键自增"
      },
      document_id: {
        type: DataTypes.UUID,
        allowNull: false,
        unique: true,
        comment: "关联的文档ID"
      },
      room_name: {
        type: DataTypes.STRING(255),
        allowNull: false,
        comment: "Yjs房间名称"
      },
      yjs_state: {
        type: DataTypes.BLOB("long" as any),
        allowNull: false,
        comment: "Yjs文档完整状态"
      },
      yjs_updates: {
        type: DataTypes.JSON,
        allowNull: true,
        comment: "Yjs增量更新记录，JSON数组格式"
      },
      last_updated: {
        type: DataTypes.DATE,
        defaultValue: DataTypes.NOW,
        comment: "最后更新时间"
      },
      version: {
        type: DataTypes.INTEGER,
        defaultValue: 1,
        comment: "文档版本号"
      },
      created_at: {
        type: DataTypes.DATE,
        defaultValue: DataTypes.NOW,
        comment: "创建时间"
      },
      updated_at: {
        type: DataTypes.DATE,
        defaultValue: DataTypes.NOW,
        comment: "更新时间"
      }
    },
    {
      sequelize,
      tableName: "yjs_documents",
      modelName: "YjsDocument",
      timestamps: true,
      underscored: true,
      indexes: [
        {
          fields: ["document_id"],
          name: "idx_document_id",
          unique: true
        },
        {
          fields: ["room_name"],
          name: "idx_room_name"
        },
        {
          fields: ["last_updated"],
          name: "idx_last_updated"
        }
      ],
      comment: "Yjs文档持久化表"
    }
  );

  return YjsDocument;
}

/**
 * 定义YjsDocument模型关联关系
 */
export function defineYjsDocumentAssociations() {
  const { Document } = require("./Document");

  // YjsDocument属于Document
  YjsDocument.belongsTo(Document, {
    foreignKey: "document_id",
    as: "document"
  });

  // Document有一个YjsDocument
  Document.hasOne(YjsDocument, {
    foreignKey: "document_id",
    as: "yjsDocument"
  });
}
